﻿using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace RSkor.Common.WinForms
{
    public class DataGridViewTextBoxCellEx : DataGridViewTextBoxCell, ISpannedCell
    {
        #region Fields
        private int _columnSpan = 1;
        private int _rowSpan = 1;
        private DataGridViewTextBoxCellEx _ownerCell;
        #endregion

        #region Properties

        public int ColumnSpan
        {
            get { return _columnSpan; }
            set
            {
                if (DataGridView == null || _ownerCell != null)
                    return;
                if (value < 1 || ColumnIndex + value - 1 >= DataGridView.ColumnCount)
                    throw new System.ArgumentOutOfRangeException("value");
                if (_columnSpan != value)
                    SetSpan(value, _rowSpan);
            }
        }

        public int RowSpan
        {
            get { return _rowSpan; }
            set
            {
                if (DataGridView == null || _ownerCell != null)
                    return;
                if (value < 1 || RowIndex + value - 1 >= DataGridView.RowCount)
                    throw new System.ArgumentOutOfRangeException("value");
                if (_rowSpan != value)
                    SetSpan(_columnSpan, value);
            }
        }

        public DataGridViewCell OwnerCell
        {
            get { return _ownerCell; }
            private set { _ownerCell = value as DataGridViewTextBoxCellEx; }
        }

        public override bool ReadOnly
        {
            get
            {
                return base.ReadOnly;
            }
            set
            {
                base.ReadOnly = value;

                if (_ownerCell == null
                    && (_columnSpan > 1 || _rowSpan > 1)
                    && DataGridView != null)
                {
                    foreach (var col in Enumerable.Range(ColumnIndex, _columnSpan))
                        foreach (var row in Enumerable.Range(RowIndex, _rowSpan))
                            if (col != ColumnIndex || row != RowIndex)
                            {
                                DataGridView[col, row].ReadOnly = value;
                            }
                }
            }
        }

        #endregion

        #region Painting.

        protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
        {
            if (_ownerCell != null && _ownerCell.DataGridView == null)
                _ownerCell = null; //owner cell was removed.

            if (DataGridView == null
                || (_ownerCell == null && _columnSpan == 1 && _rowSpan == 1))
            {
                base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle,
                        paintParts);
                return;
            }

            var ownerCell = this;
            var columnIndex = ColumnIndex;
            var columnSpan = _columnSpan;
            var rowSpan = _rowSpan;
            if (_ownerCell != null)
            {
                ownerCell = _ownerCell;
                columnIndex = _ownerCell.ColumnIndex;
                rowIndex = _ownerCell.RowIndex;
                columnSpan = _ownerCell.ColumnSpan;
                rowSpan = _ownerCell.RowSpan;
                value = _ownerCell.GetValue(rowIndex);
                errorText = _ownerCell.GetErrorText(rowIndex);
                cellState = _ownerCell.State;
                cellStyle = _ownerCell.GetInheritedStyle(null, rowIndex, true);
                formattedValue = _ownerCell.GetFormattedValue(value,
                    rowIndex, ref cellStyle, null, null, DataGridViewDataErrorContexts.Display);
            }
            if (CellsRegionContainsSelectedCell(columnIndex, rowIndex, columnSpan, rowSpan))
                cellState |= DataGridViewElementStates.Selected;
            var cellBounds2 = DataGridViewCellExHelper.GetSpannedCellBoundsFromChildCellBounds(
                this,
                cellBounds,
                DataGridView.SingleVerticalBorderAdded(),
                DataGridView.SingleHorizontalBorderAdded());
            clipBounds = DataGridViewCellExHelper.GetSpannedCellClipBounds(ownerCell, cellBounds2,
                DataGridView.SingleVerticalBorderAdded(),
                DataGridView.SingleHorizontalBorderAdded());
            using (var g = DataGridView.CreateGraphics())
            {
                g.SetClip(clipBounds);
                //Paint the content.
                advancedBorderStyle = DataGridViewCellExHelper.AdjustCellBorderStyle(ownerCell);
                ownerCell.NativePaint(g, clipBounds, cellBounds2, rowIndex, cellState,
                    value, formattedValue, errorText,
                    cellStyle, advancedBorderStyle,
                    paintParts & ~DataGridViewPaintParts.Border);
                //Paint the borders.
                if ((paintParts & DataGridViewPaintParts.Border) != DataGridViewPaintParts.None)
                {
                    var leftTopCell = ownerCell;
                    var advancedBorderStyle2 = new DataGridViewAdvancedBorderStyle
                                                   {
                                                       Left = advancedBorderStyle.Left,
                                                       Top = advancedBorderStyle.Top,
                                                       Right = DataGridViewAdvancedCellBorderStyle.None,
                                                       Bottom = DataGridViewAdvancedCellBorderStyle.None
                                                   };
                    leftTopCell.PaintBorder(g, clipBounds, cellBounds2, cellStyle, advancedBorderStyle2);

                    var rightBottomCell = DataGridView[columnIndex + columnSpan - 1, rowIndex + rowSpan - 1] as DataGridViewTextBoxCellEx
                                          ?? this;
                    var advancedBorderStyle3 = new DataGridViewAdvancedBorderStyle
                    {
                        Left = DataGridViewAdvancedCellBorderStyle.None,
                        Top = DataGridViewAdvancedCellBorderStyle.None,
                        Right = advancedBorderStyle.Right,
                        Bottom = advancedBorderStyle.Bottom
                    };
                    rightBottomCell.PaintBorder(g, clipBounds, cellBounds2, cellStyle, advancedBorderStyle3);
                }
            }
        }

        private void NativePaint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
        {
            base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
        }

        #endregion

        #region Spanning.

        private void SetSpan(int columnSpan, int rowSpan)
        {
            int prevColumnSpan = _columnSpan;
            int prevRowSpan = _rowSpan;
            _columnSpan = columnSpan;
            _rowSpan = rowSpan;

            if (DataGridView != null)
            {
                // clear.
                foreach (int rowIndex in Enumerable.Range(RowIndex, prevRowSpan))
                    foreach (int columnIndex in Enumerable.Range(ColumnIndex, prevColumnSpan))
                    {
                        var cell = DataGridView[columnIndex, rowIndex] as DataGridViewTextBoxCellEx;
                        if (cell != null)
                            cell.OwnerCell = null;
                    }

                // set.
                foreach (int rowIndex in Enumerable.Range(RowIndex, _rowSpan))
                    foreach (int columnIndex in Enumerable.Range(ColumnIndex, _columnSpan))
                    {
                        var cell = DataGridView[columnIndex, rowIndex] as DataGridViewTextBoxCellEx;
                        if (cell != null && cell != this)
                        {
                            if (cell.ColumnSpan > 1) cell.ColumnSpan = 1;
                            if (cell.RowSpan > 1) cell.RowSpan = 1;
                            cell.OwnerCell = this;
                        }
                    }

                OwnerCell = null;
                DataGridView.Invalidate();
            }
        }

        #endregion

        #region Editing.

        public override Rectangle PositionEditingPanel(Rectangle cellBounds, Rectangle cellClip, DataGridViewCellStyle cellStyle, bool singleVerticalBorderAdded, bool singleHorizontalBorderAdded, bool isFirstDisplayedColumn, bool isFirstDisplayedRow)
        {
            if (_ownerCell == null
                && _columnSpan == 1 && _rowSpan == 1)
            {
                return base.PositionEditingPanel(cellBounds, cellClip, cellStyle, singleVerticalBorderAdded, singleHorizontalBorderAdded, isFirstDisplayedColumn, isFirstDisplayedRow);
            }

            var ownerCell = this;
            if (_ownerCell != null)
            {
                var rowIndex = _ownerCell.RowIndex;
                cellStyle = _ownerCell.GetInheritedStyle(null, rowIndex, true);
                _ownerCell.GetFormattedValue(_ownerCell.Value, rowIndex, ref cellStyle, null, null, DataGridViewDataErrorContexts.Formatting);
                var editingControl = DataGridView.EditingControl as IDataGridViewEditingControl;
                if (editingControl != null)
                {
                    editingControl.ApplyCellStyleToEditingControl(cellStyle);
                    var editingPanel = DataGridView.EditingControl.Parent;
                    if (editingPanel != null)
                        editingPanel.BackColor = cellStyle.BackColor;
                }
                ownerCell = _ownerCell;
            }
            cellBounds = DataGridViewCellExHelper.GetSpannedCellBoundsFromChildCellBounds(
                this,
                cellBounds,
                singleVerticalBorderAdded,
                singleHorizontalBorderAdded);
            cellClip = DataGridViewCellExHelper.GetSpannedCellClipBounds(ownerCell, cellBounds, singleVerticalBorderAdded, singleHorizontalBorderAdded);
            return base.PositionEditingPanel(
                 cellBounds, cellClip, cellStyle,
                 singleVerticalBorderAdded,
                 singleHorizontalBorderAdded,
                 ownerCell.InFirstDisplayedColumn(),
                 ownerCell.InFirstDisplayedRow());
        }

        protected override object GetValue(int rowIndex)
        {
            if (_ownerCell != null)
                return _ownerCell.GetValue(_ownerCell.RowIndex);
            return base.GetValue(rowIndex);
        }

        protected override bool SetValue(int rowIndex, object value)
        {
            if (_ownerCell != null)
                return _ownerCell.SetValue(_ownerCell.RowIndex, value);
            return base.SetValue(rowIndex, value);
        }

        #endregion

        #region Other overridden

        protected override void OnDataGridViewChanged()
        {
            base.OnDataGridViewChanged();

            if (DataGridView == null)
            {
                _columnSpan = 1;
                _rowSpan = 1;
            }
        }

        protected override Rectangle BorderWidths(DataGridViewAdvancedBorderStyle advancedBorderStyle)
        {
            if (_ownerCell == null
                && _columnSpan == 1 && _rowSpan == 1)
            {
                return base.BorderWidths(advancedBorderStyle);
            }

            if (_ownerCell != null)
                return _ownerCell.BorderWidths(advancedBorderStyle);

            var leftTop = base.BorderWidths(advancedBorderStyle);
            var rightBottomCell = DataGridView[
                ColumnIndex + ColumnSpan - 1,
                RowIndex + RowSpan - 1] as DataGridViewTextBoxCellEx;
            var rightBottom = rightBottomCell != null
                ? rightBottomCell.NativeBorderWidths(advancedBorderStyle)
                : leftTop;
            return new Rectangle(leftTop.X, leftTop.Y, rightBottom.Width, rightBottom.Height);
        }

        private Rectangle NativeBorderWidths(DataGridViewAdvancedBorderStyle advancedBorderStyle)
        {
            return base.BorderWidths(advancedBorderStyle);
        }

        protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize)
        {
            if (OwnerCell != null) return new Size(0, 0);
            var size = base.GetPreferredSize(graphics, cellStyle, rowIndex, constraintSize);
            var grid = DataGridView;
            var width = size.Width - Enumerable.Range(ColumnIndex + 1, ColumnSpan - 1)
                                           .Select(index => grid.Columns[index].Width)
                                           .Sum();
            var height = size.Height - Enumerable.Range(RowIndex + 1, RowSpan - 1)
                                           .Select(index => grid.Rows[index].Height)
                                           .Sum();
            return new Size(width, height);
        }

        #endregion

        #region Private Methods

        private bool CellsRegionContainsSelectedCell(int columnIndex, int rowIndex, int columnSpan, int rowSpan)
        {
            if (DataGridView == null)
                return false;

            return (from col in Enumerable.Range(columnIndex, columnSpan)
                    from row in Enumerable.Range(rowIndex, rowSpan)
                    where DataGridView[col, row].Selected
                    select col).Any();
        }

        #endregion

        #region ISpannedCell Members

        public void ResetSpan()
        {
            SetSpan(ColumnSpan, RowSpan);
        }

        #endregion
    }

    public class DataGridViewTextBoxColumnEx : DataGridViewColumn
    {
        #region ctor
        public DataGridViewTextBoxColumnEx()
            : base(new DataGridViewTextBoxCellEx())
        {
        }
        #endregion

        protected override void OnDataGridViewChanged()
        {
            base.OnDataGridViewChanged();
            if (DataGridView != null)
            {
                for (int rowIdx = 0; rowIdx < DataGridView.RowCount; rowIdx++)
                {
                    ISpannedCell cell = DataGridView[this.Index, rowIdx] as ISpannedCell;
                    if (cell.ColumnSpan > 1 || cell.RowSpan > 1)
                        cell.ResetSpan();
                }
            }
        }
    }
}
